﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mono.Cecil;
using CustomTypes.Interfaces;
using devtm.AutoMapper;
using System.Collections;
using System.Diagnostics;

namespace CustomTypes.Mono
{

    [DebuggerDisplay("{AssemblyName}")]
    public class AssemblyTypeMonoDescriptor : IAssemblyDescriptor
    {


        private string _filename;
        private DateTime lastWrite;
        private AssemblyDefinition assembly;
        private ReaderParameters parameters;


        public AssemblyTypeMonoDescriptor(devtm.AutoMapper.TypeRepository repository, string filename)
        {

            this.Repository = repository;
            _filename = filename;
            lastWrite = DateTime.MinValue;

            parameters = new ReaderParameters()
            {
                ReadingMode = ReadingMode.Immediate,
                AssemblyResolver = new CustomAssemblyResolver(repository),
            };

        }

        #region assembly

        private AssemblyDefinition GetAssembly()
        {

            if (lastWrite < System.IO.File.GetLastWriteTime(_filename))
                assembly = null;

            return assembly ?? (assembly = CreateAssembly());

        }

        private AssemblyDefinition CreateAssembly()
        {
            lastWrite = System.IO.File.GetLastWriteTime(_filename);
            return AssemblyDefinition.ReadAssembly(_filename, parameters);
        }

        #endregion


        #region Implement ITypeContext

        public string AssemblyName { get { return GetAssembly().FullName; } }

        public string ShortAssemblyName { get { return GetAssembly().Name.Name; } }


        public TypeDefinition GetType(string @namespace, string name)
        {

            AssemblyDefinition ass = GetAssembly();

            var types = ass.MainModule.GetTypes();

            foreach (TypeDefinition type in types)
                if (type.Name == name)
                    if (type.Namespace == @namespace)
                        return type;

            return null;

        }


        public IEnumerable<TypeDefinition> GetTypes(HashSet<string> namespaces)
        {

            AssemblyDefinition ass = GetAssembly();

            var types = ass.MainModule.GetTypes();

            foreach (TypeDefinition type in types)
                if (type.Name != "<Module>")
                    if (namespaces == null || @namespaces.Contains(type.Namespace))
                        yield return type;

        }

        public IEnumerable<TypeDefinition> GetTypes(Predicate<TypeDefinition> predicate, HashSet<string> namespaces = null)
        {
            AssemblyDefinition ass = GetAssembly();

            var types = ass.MainModule.GetTypes();

            foreach (TypeDefinition type in types)
                if (type.Name != "<Module>")
                    if (predicate == null || predicate(type))
                        if (namespaces == null || @namespaces.Contains(type.Namespace))
                            yield return type;

        }

        public void Refresh() { assembly = null; }
        public void Clear() { assembly = null; }

        #endregion


        public TypeRepository Repository { get; set; }


        public IEnumerable<string> Namespaces(Predicate<TypeDefinition> predicate)
        {

            HashSet<string> l = new HashSet<string>();

            AssemblyDefinition ass = GetAssembly();
            var types = ass.MainModule.GetTypes();

            foreach (TypeDefinition type in types)
                if (type.Name != "<Module>")
                {

                    if (predicate == null || predicate(type))
                    {
                        string n = type.Namespace;
                        if (!l.Contains(n))
                        {
                            l.Add(n);
                            yield return n;
                        }
                    }

                }

            l.Clear();


        }



    }
}
